package cn.wenhaha.common.utils.spring;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

public class DynamicRouteUtils {

    /** 路径->MappingInfo **/
    private static final Map<String, RequestMappingInfo> registry = new HashMap<>();

    /** controller->路径 **/
    private static final Map<Class<?>, List<String>> controller = new HashMap<>();

    private static final   Logger logger = LoggerFactory.getLogger(DynamicRouteUtils.class);

    public static  void registerController(Class<?> clazz){
        registerController(clazz,null);
    }

    public static  void registerController(Class<?> clazz,String prefix){
        String handler = SpringUtils.getBeanRegClassName(clazz);
        if (!StringUtils.hasLength(handler)){
            return;
        }
        ApplicationContext applicationContext=SpringUtils.getApplicationContext();
        RequestMappingHandlerMapping bean = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Class<?> type = applicationContext.getType(handler);
        RequestMappingInfo.BuilderConfiguration builderConfiguration = bean.getBuilderConfiguration();
        Class<?> userType = ClassUtils.getUserClass(type);
        Map<Method, RequestMappingInfo> methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<RequestMappingInfo>) method -> {
                    try {
                        return getMappingForMethod(method, builderConfiguration,userType,prefix);
                    }
                    catch (Throwable ex) {
                        throw new IllegalStateException("Invalid mapping on handler class [" +
                                userType.getName() + "]: " + method, ex);
                    }
                });
        synchronized (DynamicRouteUtils.class){
            List<String> pathList = new ArrayList<>(methods.size());
            methods.forEach((method, mapping) -> {
                assert mapping.getPathPatternsCondition() != null;
                Set<PathPattern> patterns = mapping.getPathPatternsCondition().getPatterns();
                List<String> patternsList = patterns.stream().map(PathPattern::getPatternString)
                        .collect(Collectors.toList());
                pathList.addAll(patternsList);
                patternsList.forEach(path -> {
                    if (registry.containsKey(path)){
                        bean.unregisterMapping(registry.get(path));
                    }
                    registry.put(path,mapping);
                    logger.debug("动态注册路由{}",path);
                });
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                bean.registerMapping(mapping,handler,invocableMethod);
            });
            controller.put(userType,pathList);
        }
    }


    public static  void unregisterMapping(Class<?> clazz){
        RequestMappingHandlerMapping mappingHandlerMapping = SpringUtils.getBean(RequestMappingHandlerMapping.class);
        synchronized (DynamicRouteUtils.class){
            if (!controller.containsKey(clazz)){
                return;
            }
            controller.remove(clazz).forEach(p->{
                if (registry.containsKey(p)){
                    logger.debug("卸载路由{}",p);
                    mappingHandlerMapping.unregisterMapping(registry.remove(p));
                }
            });
        }
    }


    private static RequestMappingInfo getMappingForMethod(Method method, RequestMappingInfo.BuilderConfiguration builderConfiguration, Class<?> handlerType,String prefix) {
        RequestMappingInfo info = createRequestMappingInfo(method,builderConfiguration);
        if (info != null) {
            RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType,builderConfiguration);
            if (typeInfo != null) {
                info = typeInfo.combine(info);
            }
            if (prefix != null) {
                info = RequestMappingInfo.paths(prefix).options(builderConfiguration).build().combine(info);
            }
        }
        return info;
    }



    private static RequestMappingInfo createRequestMappingInfo(AnnotatedElement element, RequestMappingInfo.BuilderConfiguration config){
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        RequestMappingInfo.Builder builder = RequestMappingInfo
                .paths(requestMapping.path()[0])
                .methods(requestMapping.method())
                .params(requestMapping.params())
                .headers(requestMapping.headers())
                .consumes(requestMapping.consumes())
                .produces(requestMapping.produces())
                .mappingName(requestMapping.name());
        return  builder.options(config).build();
    }

}
